/*
 *  $Id: Abstract.cs 1195 2010-11-19 15:17:06Z thenn.erannor $
 *	This file is a part of Ragima CCG.
 *	(C) 2008 Ragima development group.
 *
 *	Licensed under the terms of the GNU GPL License version 2.
 *
 */

using System;
using System.Collections.Generic;

using AgateLib.DisplayLib;
using AgateLib.Geometry;

namespace Ragima {
    /// <summary>
    /// Абстрактный игровой объект
    /// Имеет здоровье, эффекты, и способности.
    /// </summary>
    public abstract class AbstractObject: Languages.INamed, IHintable {
        private List<Affect> _affects;
        public readonly AffectSummary Summary;
        private List<AbilityCard> _usedAbilities;
        private HealthChangeHandler _healthChange;
        private bool _death;

        public AbstractObject() {
            _affects = new List<Affect>();
            Summary = new AffectSummary();
            _usedAbilities = new List<AbilityCard>();
            _death = false;
        }

        /// <value>
        /// Игрок-владелец объекта
        /// </value>
        public abstract Player Owner { get; }

        /// <value>
        /// Имя/название объекта
        /// </value>
        public abstract Languages.Names Name { get; }

        /// <value>
        /// Раса объекта
        /// </value>
        public virtual Race Race { get { return RaceManager.GetRace<Races.Unknown>(); } }

        /// <value>
        /// Текущее здоровье
        /// </value>
        public abstract int Health { get; set; }
        /// <value>
        /// Минимальное здоровье
        /// </value>
        public abstract int MinHealth { get; }
        /// <value>
        /// Максимальное здоровье
        /// </value>
        public abstract int MaxHealth { get; }

        /// <value>
        /// Разница между текущим и минимальным здоровьем
        /// </value>
        public int HealthToMin { get { return Health - MinHealth; } }
        /// <value>
        /// Разница между максимальным и текущим здоровьем
        /// </value>
        public int HealthToMax { get { return Math.Max(MaxHealth - Health, 0); } }
        /// <value>
        /// Полное здоровье (разница между максимальным и минимальным)
        /// </value>
        public int FullHealth { get { return MaxHealth - MinHealth; } }

        /// <value>
        /// Является ли объект мертвым (с учетом бессмертия)
        /// </value>
        public virtual bool IsDeath { get { return (_death || Health == MinHealth) && !HasFlag<Affects.Immortal>(); } }

        /// <summary>
        /// Установка смерти
        /// </summary>
        public void Death() { _death = true; }
        /// <summary>
        /// Сброс смерти
        /// </summary>
        public void ResetDeath() { _death = false; }

        /// <summary>
        /// Мгновенная смерть
        /// </summary>
        public abstract void InstantDeath();

        /// <value>
        /// Глобальный суммарный эффект
        /// </value>
        public virtual AffectSummary GlobalSummary { get { return Summary; } }

        /// <value>
        /// Глобальный вражеский суммарный эффект
        /// </value>
        public AffectSummary EnemySummary { get { return Owner.Enemy.GlobalSummary; } }

        /// <summary>
        /// Получение списка всех способностей объекта
        /// </summary>
        /// <returns>Список способностей</returns>
        public List<AbilityCard> GetAbilities() {
            return Summary.GetAbilities();
        }

        /// <summary>
        /// Проверка, можно ли использовать эту способность
        /// </summary>
        /// <param name="ability">Проверяемая способность</param>
        /// <returns>true, если можно</returns>
        public virtual bool CanUseAbility(AbilityCard ability, bool notice) {
            if (IsUsedAbility(ability)) {
                if (notice) Game.Current.ErrorNotice(Resources.Game.AbilityAlreadyUse.GetText());
                return false;
            }
            return ability.TestConditionsFor(this, notice);
        }

        /// <summary>
        /// Установка всех эффектов на объекте в суммарный эффект
        /// </summary>
        public void SetAffects() {
            foreach (Affect affect in _affects) affect.SetAffect();
        }

        /// <summary>
        /// Сброс всех эффектов на объекте из суммарного эффекта
        /// </summary>
        public void ResetAffects() {
            foreach (Affect affect in _affects) affect.ResetAffect();
        }

        /// <summary>
        /// Проверка наличия эффекта определенного типа
        /// </summary>
        /// <returns>true, если эффект есть</returns>
        public bool HasAffect<T>() where T: Affect { return HasAffect<T>(null); }
        /// <summary>
        /// Проверка наличия эффекта определенного типа
        /// </summary>
        /// <returns>true, если эффект есть</returns>
        public bool HasAffect(Type type) {
            foreach (Affect affect in _affects) if (affect.GetType() == type) return true;
            return false;
        }
        /// <summary>
        /// Проверка наличия эффекта определенного типа, соответствующего условию
        /// </summary>
        /// <param name="predicate">Предикат условия поиска</param>
        /// <returns>true, если эффект есть</returns>
        public bool HasAffect<T>(Predicate<T> predicate) where T: Affect {
            foreach (Affect affect in _affects) if (affect is T) {
                if (predicate == null || predicate((T) affect)) return true;
            }
            return false;
        }
        /// <summary>
        /// Проверка наличия определенного эффекта на верхнем уровне
        /// </summary>
        /// <param name="affect">Искомый эффект</param>
        /// <returns>true, если эффект есть</returns>
        public bool HasAffect(Affect affect) {
            return _affects.Contains(affect);
        }
        /// <summary>
        /// Проверка наличия эффекта-флага определенного типа
        /// </summary>
        /// <returns>true, если эффект есть</returns>
        public bool HasFlag<T>() where T: FlagAffect {
            return Summary.Flags[typeof(T)];
        }

        /// <summary>
        /// Добавление эффекта
        /// </summary>
        public void AddAffect(Affect affect) {
            affect.Owner = this;
            _affects.Add(affect);
            affect.SetAffect();
            if (!(affect is NaturalAffect)) SetAffectFlash();
        }

        /// <summary>
        /// Добавление эффекта определенного типа
        /// Допустимы только эффекты, не имеющие параметров
        /// </summary>
        public void AddAffect<T>() where T: Affect, new() {
            AddAffect(new T());
        }

        /// <summary>
        /// Поиск эффекта определенного типа
        /// </summary>
        /// <returns>Найденный эффект, либо null, если нет такого</returns>
        public T GetAffect<T>() where T: Affect { return GetAffect<T>(false); }

        /// <summary>
        /// Поиск эффекта определенного типа
        /// </summary>
        /// <param name="deep">true, если глубокий поиск</param>
        /// <returns>Найденный эффект, либо null, если нет такого</returns>
        public T GetAffect<T>(bool deep) where T: Affect {
            foreach (Affect affect in GetAffects(deep))
                if (affect is T) return (T) affect;
            return null;
        }

        /// <summary>
        /// Поиск эффекта определенного типа
        /// </summary>
        /// <param name="predicate">Предикат условия поиска</param>
        /// <returns>Найденный эффект, либо null, если нет такого</returns>
        public T GetAffect<T>(Predicate<T> predicate) where T: Affect { return GetAffect<T>(false, predicate); }

        /// <summary>
        /// Поиск эффекта определенного типа
        /// </summary>
        /// <param name="deep">true, если глубокий поиск</param>
        /// <param name="predicate">Предикат условия поиска</param>
        /// <returns>Найденный эффект, либо null, если нет такого</returns>
        public T GetAffect<T>(bool deep, Predicate<T> predicate) where T: Affect {
            foreach (Affect affect in GetAffects(deep))
                if (affect is T && predicate((T) affect)) return (T) affect;
            return null;
        }

        /// <summary>
        /// Получить список всех эффектов
        /// </summary>
        public IEnumerable<Affect> GetAffects(bool deep) {
            return deep ? GetDeepAffects(_affects) : _affects;
        }

        private static IEnumerable<Affect> GetDeepAffects(IEnumerable<Affect> affects) {
            foreach (Affect affect in affects) {
                yield return affect;
                if (affect is GroupAffect)
                    foreach (Affect sub in GetDeepAffects((GroupAffect) affect))
                        yield return sub;
            }
        }

        /// <summary>
        /// Получить список эффектов по условию
        /// </summary>
        /// <param name="predicate">Предикат условия поиска</param>
        public IEnumerable<Affect> GetAffects(bool deep, Predicate<Affect> predicate) {
            foreach (Affect affect in GetAffects(deep)) if (predicate(affect)) yield return affect;
        }

        /// <summary>
        /// Получить список эффектов-чар
        /// </summary>
        public IEnumerable<EnchantmentAffect> GetEnchantments() {
            foreach (Affect affect in _affects)
                if (affect is EnchantmentAffect)
                    yield return (EnchantmentAffect) affect;
        }

        /// <summary>
        /// Удаление эффекта
        /// </summary>
        /// <param name="affect">Удаляемый эффект</param>
        public void RemoveAffect(Affect affect) {
            if (_affects.Contains(affect)) {
                affect.ResetAffect();
                _affects.Remove(affect);
                affect.Owner = null;
                affect.OnRemove();
                SetAffectFlash();
            }
        }
        /// <summary>
        /// Удаление всех эффектов
        /// </summary>
        public void RemoveAffects() {
            RemoveAffects(delegate(Affect affect) { return true; });
        }

        /// <summary>
        /// Удаление эффекта определенного типа
        /// </summary>
        public void RemoveAffect<T>() where T: Affect {
            T affect = GetAffect<T>();
            if (affect != null) RemoveAffect(affect);
        }
        /// <summary>
        /// Удаление эффектов определенного типа
        /// </summary>
        public void RemoveAffects<T>() where T: Affect {
            RemoveAffects(delegate(Affect affect) { return affect is T; });
        }

        /// <summary>
        /// Удаление завершенных эффектов
        /// </summary>
        public void RemoveEndedAffects() {
            RemoveAffects(delegate(Affect affect) { return affect.IsEnded; });
        }

        /// <summary>
        /// Рассеивание эффектов, поддающихся ему
        /// </summary>
        public void DispellAffects() {
            List<Affect> dispelled = _affects.FindAll(delegate(Affect affect) { return affect.Dispellable; });
            foreach (Affect affect in dispelled)
                affect.Dispell();
        }

        public void RemoveAffects(Predicate<Affect> predicate) {
            int count = _affects.RemoveAll(delegate(Affect affect) {
                if (predicate(affect)) {
                    affect.ResetAffect();
                    affect.Owner = null;
                    affect.OnRemove();
                    return true;
                } else
                    return false;
            });
            if (count > 0)
                SetAffectFlash();
        }

        protected void SetAffectFlash() {
            if (_healthChange == null && Owner != null && !Owner.Game.FastPhase)
                _healthChange = new HealthChangeHandler(Color.White);
        }

        /// <summary>
        /// Проверка, использована ли способность
        /// </summary>
        public bool IsUsedAbility(AbilityCard ability) {
            return _usedAbilities.Contains(ability);
        }

        /// <summary>
        /// Установка факта использования способности
        /// </summary>
        public void SetUsedAbility(AbilityCard ability) {
            if (!IsUsedAbility(ability))
                _usedAbilities.Add(ability);
        }

        /// <value>
        /// Является ли это существо/игрок нежитью
        /// </value>
        public bool IsUndead {
            get { return HasFlag<Affects.Undead>(); }
        }

        /// <value>
        /// Является ли это существо/игрок духом
        /// </value>
        public bool IsSpirit {
            get { return HasFlag<Affects.Spirit>(); }
        }

        /// <summary>
        /// Нанесение урона
        /// </summary>
        /// <param name="damage">Информация об уроне</param>
        /// <returns>Нанесенный урон</returns>
        public int DamageBy(DamageInfo damage) {
            damage.CorrectDamageByTarget(this);
            HealthChange(damage, MaxHealth);
            return damage.RealDamage;
        }

        public int DamageDirectBy(DamageInfo damage, bool ignoreModTaken) {
            damage.Direct = true;
            damage.IgnoreModTaken = ignoreModTaken;
            return DamageBy(damage);
        }

        /// <summary>
        /// Лечение
        /// </summary>
        /// <param name="heal">Информация о лечении</param>
        /// <returns>Реальное лечение</returns>
        public int HealBy(DamageInfo heal) {
            return HealBy(heal, MaxHealth);
        }

        /// <summary>
        /// Лечение
        /// </summary>
        /// <param name="heal">Информация о лечении</param>
        /// <param name="maxHealth">Максимальное здоровье</param>
        /// <returns>Реальное лечение</returns>
        public int HealBy(DamageInfo heal, int maxHealth) {
            heal.FullDamage = Math.Min(heal.FullDamage, Math.Max(maxHealth - Health, 0));
            heal.IsHeal = true;
            heal.CorrectDamageByTarget(this);
            HealthChange(heal, maxHealth);
            return heal.RealDamage;
        }

        public void SetHealth(int newHealth) {
            _healthChange = new HealthChangeHandler(newHealth > Health, newHealth - Health, DamageType.Health);
            Health = newHealth;
        }

        private void HealthChange(DamageInfo damage, int maxHealth) {
            if (damage.IsHeal) {
                if (damage.HealthChange <= 0) return;
                Game.Current.Log.Add(Resources.Game.Heal, this, damage.HealthChange);
                if (Health < maxHealth)
                    Health = Math.Min(Health + damage.HealthChange, maxHealth);
            } else {
                damage.RealDamage = Math.Min(damage.FullDamage, HealthToMin);
                Summary.DoBeforeDamage(this, damage);
                GlobalSummary.DoBeforeDamage(this, damage);
                if (damage.HealthChange > 0) {
                    Game.Current.Log.Add(Resources.Game.Damage, this, damage.RealDamage);
                    Health -= damage.HealthChange;
                    Summary.DoAfterDamage(this, damage);
                    GlobalSummary.DoAfterDamage(this, damage);
                }
            }
            if (HealthToMin == 0) Death();
            _healthChange = new HealthChangeHandler(damage.IsHeal, damage.RealDamage, damage.DmgType);
        }

        protected void DrawHealthChange(Rectangle rect, bool numMove) {
            if (_healthChange != null) _healthChange.Draw(rect, numMove);
        }

        protected void ProcessHealthChange() {
            if (_healthChange != null) {
                if (_healthChange.IsEnded)
                    _healthChange = null;
                else
                    _healthChange.Process();
            }
        }

        public bool IsHealthChanged { get { return _healthChange != null; } }

        /// <summary>
        /// Событие при смерти объекта
        /// </summary>
        public virtual void OnDeath() {
            Summary.DoDeath(this);
            GlobalSummary.DoDeath(this);
        }

        /// <summary>
        /// Событие при начале новой фазы атаки
        /// </summary>
        public virtual void OnStartOffencePhase() {
            _usedAbilities.Clear();
            Summary.DoStartOffencePhase();
        }

        /// <summary>
        /// Событие при конце фазы атаки
        /// </summary>
        public virtual void OnEndOffencePhase() {
            Summary.DoEndOffencePhase();
        }

        /// <summary>
        /// Событие при начале новой фазы защиты
        /// </summary>
        public virtual void OnStartDefencePhase() {
            Summary.DoStartDefencePhase();
        }

        /// <summary>
        /// Событие при конце фазы защиты
        /// </summary>
        public virtual void OnEndDefencePhase() {
            Summary.DoEndDefencePhase();
        }

        /// <summary>
        /// Событие после использования способности
        /// </summary>
        /// <param name="ability">Использованная способность</param>
        public virtual void OnAfterUseAbility(AbilityCard ability, TargetArgs args) {
            SetUsedAbility(ability);
            Summary.DoUseAbility(ability, args);
            GlobalSummary.DoUseAbility(ability, args);
        }

        /// <summary>
        /// Событие готовности к битве
        /// </summary>
        public virtual void OnBattleReady() {}

        /// <summary>
        /// Обновление цели
        /// </summary>
        public virtual void UpdateTarget(Player player) {
            if (Game.Current.HasTarget && Game.Current.Target is MoveTarget)
                Game.Current.ResetTarget();
        }

        /// <summary>
        /// Получить текущий регион для объекта.
        /// </summary>
        public virtual Region FindRegion() {
            return null;
        }

        #region IHintable implementation
        public virtual Hint GetHint(Rectangle hintRect) {
            return null;
        }
        #endregion
    }

    /// <summary>
    /// Обработчик изменения здоровья
    /// </summary>
    public class HealthChangeHandler {
        private const byte MaxAlpha = 108;
        private const int DefaultAlphaChange = 4;
        private const int AlphaAccel = 4;
        private const int FirstPause = 7;
        private const int MovementSteps = 61;
        private const int MaxStep = MovementSteps + FirstPause;

        private int _value;
        private bool _isHeal;
        private Color _color;
        private byte _alpha, _alphaGrow;
        private int _step;

        public HealthChangeHandler(bool isHeal, int value, DamageType damage): this(isHeal, value, damage.Color) {}
        public HealthChangeHandler(Color color): this(false, 0, color) {}
        public HealthChangeHandler(bool isHeal, int value, Color color) {
            _isHeal = isHeal;
            _value = Math.Abs(value);
            _color = color;
            _alpha = 0;
            _alphaGrow = DefaultAlphaChange;
            _step = 0;
        }

        public bool IsEnded { get { return _step == MaxStep; } }

        public void Draw(Rectangle rect, bool numMove) {
            if (_alpha > 0) {
                _color.A = _alpha;
                Display.FillRect(rect, _color);
            }
            if (_value != 0 && _step >= FirstPause) {
                NumberSurface numbers = _isHeal ? Data.Numbers02Green : Data.Numbers02Red;
                int offset = numMove ? _step - FirstPause : 0;
                if (_isHeal) offset = -offset;
                numbers.DrawCenter(rect.X + rect.Width/2, rect.Y + (rect.Height - Constants.Numbers02Height)/2 + offset, _value);
            }
        }

        public void Process() {
            if (_alphaGrow > 0) {
                _alpha += _alphaGrow;
                if (!_isHeal) _alphaGrow += AlphaAccel;
                if (_alpha >= MaxAlpha) _alphaGrow = 0;
            } else {
                if (_alpha >= DefaultAlphaChange)
                    _alpha -= DefaultAlphaChange;
                else
                    _alpha = 0;
            }
            if (_step < MaxStep) _step++;
        }
    }
}